<template>
  <div ref="mouseupContainer" v-loading="loading" class="chat-pdf-container" element-loading-text="加载中...">
    <div class="flexrbc header">
      <img src="../assets/images/common/ym_rect_logo.png" alt="研墨ai报告解读助手" @click="go2Yanmo" />
      <i class="el-icon-close close" @click="close"></i>
      <!-- <span @click.stop="openYanmoTip(0)" class="pointer">去官网解读更多报告<i class="el-icon-arrow-right"></i></span> -->
    </div>
    <el-scrollbar ref="scrollbarRef" class="scrollbar">
      <div class="title">《{{ reportTitle }}》报告总结：</div>
      <div class="report-detail">
        <div v-if="summary.abstracts" class="summary">
          <div class="summary__title">报告概要</div>
          <div class="summary__content">
            <span v-for="(item, index) in summary.abstracts" :key="index">
              {{ item }}
            </span>
          </div>
        </div>
        <div v-if="summary.chapters" class="chapter">
          <div class="flexrbc chapter__title">
            <span>章节速览</span>
            <div @click="chapterHide = !chapterHide">
              <span v-if="chapterHide" class="flexr0c chapter__arrow-icon pointer">
                展开
                <i class="el-icon-arrow-down"></i>
              </span>
              <span v-else class="flexr0c chapter__arrow-icon pointer">
                收起
                <i class="el-icon-arrow-up"></i>
              </span>
            </div>
          </div>
          <div ref="chapterItemContainer" class="chapter__item-container" :class="{ 'chapter__item-container--hide': chapterHide }">
            <div v-for="(item, index) in summary.chapters" :key="index" class="chapter__item">
              <div class="flexr0c">
                <img class="chapter__item__icon" src="../assets/images/common/select_icon.png" alt="章节信息" />
                <span class="fw600">{{ item.firstLevel }}</span>
              </div>
              <div v-for="text in item.secondLevels" :key="text" class="chapter__item__introduce">
                {{ text }}
              </div>
            </div>
          </div>
        </div>
        <div class="tips">研墨AI已完成全文理解，想了解这篇报告哪些内容，请提问</div>
      </div>
      <div v-if="summary.qas && summary.qas.length" class="question">
        <div class="question__title">猜你想问</div>
        <div v-for="(item, index) in summary.qas" :key="index" class="question__item pointer" @click="clickQuestion(item)">
          {{ `Q${index + 1}：${item.question}` }}
        </div>
      </div>
      <div class="chat">
        <div v-for="(item, index) in chats" :key="index">
          <div v-show="item.params && item.type === 'gpt' && item.loading" class="flexr chat__title">
            <i class="el-icon-circle-check"></i>
            <span>正在搜索：{{ item.params && item.params.content }}</span>
          </div>
          <div class="flexrbs chat__item" :class="{ 'chat__item--user': item.type !== 'gpt' }">
            <div class="chat__item__bg">
              <div class="flexrbs chat__item__info">
                <!-- 头像 -->
                <template>
                  <img v-if="item.type !== 'gpt'" class="chat__item__img" :src="userInfo.avatar || defaultAvatar" alt="头像" />
                  <img v-else class="chat__item__img" src="@/assets/images/gpt/gpt.png" alt="头像" />
                </template>
                <!-- 消息内容 -->
                <template>
                  <!-- 加载动画 -->
                  <div v-if="item.loading" class="flexr0e chat__item__content">
                    <span v-html="item.content"></span>
                    <div v-if="item.content === '搜索中'" class="flexrbc loading">
                      <div class="dot1"></div>
                      <div class="dot2"></div>
                      <div class="dot3"></div>
                    </div>
                  </div>
                  <!-- 内容显示 -->
                  <div v-else class="chat__item__content" v-html="item.content"></div>
                </template>
              </div>
              <div v-if="item.type === 'gpt' && !item.timer && !item.loading && item.pageNums && item.pageNums.length" class="chat__item__reference">
                参考文中：
                <span v-for="(num, i) in item.pageNums" :key="i" class="page" @click="clickPageNum(num)">第{{ num }}页 {{ i === item.pageNums.length - 1 ? '' : '、' }}</span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </el-scrollbar>
    <div class="flexrbc footer">
      <el-input v-model="question" class="footer__input" clearable placeholder="请输入提问内容" @keyup.enter.native="quiz"></el-input>
      <div class="footer__btn" @click="quiz">发送</div>
    </div>
    <yanmo-dialog-tip :first-content="firstContent" :url="yanmoUrl" :first-confirm-btn-name="firstConfirmBtnName" :show.sync="tipShow"></yanmo-dialog-tip>
  </div>
</template>

<script>
import Clipboard from 'clipboard'
import YanmoDialogTip from '../YanmoDialogTip.vue'
import _ from 'lodash'

export default {
  name: 'ChatPdf',
  components: { YanmoDialogTip },
  props: {
    reportId: {
      type: [Number, String],
      required: true,
    },
    reportTitle: {
      type: String,
      required: true,
      default: '',
    },
    maxPageNum: {
      type: Number,
      default: 0,
    },
  },
  data() {
    return {
      fileId: '',
      yanmoUrl: '',
      firstContent: '',
      firstConfirmBtnName: '',
      loading: true,
      chapterHide: false,
      question: '',
      show: false,
      chats: [],
      tipShow: false,
      mouseupContainer: null,
      summary: {},
      qasMap: {},
      maxHistoryNum: 30, // 自由问答history数组最大传过去的数量
    }
  },
  computed: {
    userInfo() {
      return this.$store.state.user.userInfo
    },
    defaultAvatar() {
      return new URL('../assets/images/user/avatar.jpg', import.meta.url).href
    },
  },
  mounted() {
    this.init()
  },
  destroyed() {
    // 关闭AI解析框后也需要支持弹出翻译的gpt弹窗
    // this.$eventHub.$off('use-gpt')
    this.chats?.map((item) => {
      if (item.timer) {
        this.stop(data)
      }
    })
  },
  methods: {
    close() {
      this.$emit('close')
    },
    clickPageNum(num) {
      this.$emit('clickPageNum', num)
    },
    async init() {
      await this.getSummaryDate()
      this.chats = this.getChatRecords()
      this.getIsYanmoUser()
      this.$nextTick().then(() => {
        this.mouseupContainer = this.$refs['mouseupContainer']
        const chapterItemContainer = this.$refs['chapterItemContainer']
        if (chapterItemContainer) {
          const height = chapterItemContainer.getBoundingClientRect().height
          chapterItemContainer.style.height = `${height}px`
        }
      })
    },
    async getSummaryDate() {
      const res = await this.$getYm(`/chatdoc/dj/${this.reportId}`)
      const summary = res.data?.data?.summary
      if (!summary) {
        this.$message({
          type: 'error',
          message: '该报告暂时无法进行AI解读',
        })
        return
      }
      const abstracts = summary?.abstract?.split(/\n+/) || []
      const chapters =
        summary?.chapters?.map((item) => {
          const secondLevels = []
          Object.keys(item).forEach((key) => {
            if (key.startsWith('second-level')) {
              secondLevels.push(item[key])
            }
          })
          return { secondLevels, firstLevel: item['first-level'] }
        }) || []
      const qasMap = {}
      summary.qas?.forEach((item) => {
        qasMap[item.question] = item
      })
      this.qasMap = qasMap
      this.summary = { ...summary, abstracts, chapters }
      this.fileId = res.data.data.id
      this.loading = false
    },
    async getIsYanmoUser() {
      if (!this.hasYanmoAccount) {
        const { hasYanmoAccount } = await this.$get('user/extra?field=has_yanmo_account')
        this.$store.dispatch('setHasYanmoAccount', hasYanmoAccount === 'true')
      }
    },
    stop(item) {
      item.timer && clearInterval(item.timer)
      item.timer = undefined
    },
    copy(message) {
      Clipboard.copy(message)
      this.$message({
        type: 'success',
        message: '已复制',
      })
    },
    refresh(data) {
      this.useGpt(data)
    },
    outputContent(data, list) {
      data.timer = setInterval(() => {
        data.content += list.shift()
        if (list.length === 0 || list.length % 10 === 0) {
          this.$nextTick().then(() => {
            this.scrollTo()
          })
        }
        if (list.length === 0) {
          this.stop(data)
        }
      }, 50)
    },
    scrollTo() {
      this.$refs?.scrollbarRef.wrap.scrollTo({
        top: this.$refs.scrollbarRef.wrap.scrollHeight,
        behavior: 'smooth',
      })
      this.$refs?.scrollbarRef.update()
    },
    updateChats(data, type = 'add', isOutput = true) {
      const list = data.content.split('')
      const isAnimation = data.type === 'gpt' && isOutput
      if (type === 'update') {
        data.completeContent = data.content
        this.stop(data) // 停止正在输出文字的定时器
      }
      if (isAnimation) {
        data.content = list.shift()
      }
      if (type === 'add') {
        this.chats.push(data)
      }
      this.setChatRecords()
      this.$nextTick().then(() => {
        this.scrollTo()
        if (isAnimation) {
          this.outputContent(data, list)
        }
      })
    },
    openYanmoTip(index = 0) {
      this.firstContent = ['此功能由研墨AI支持首次使用AI概念解析、AI翻译功能需要去研墨AI注册激活既可免费试用', '抱歉，您本月的AI功能已达字数上限，继续使用，请前往研墨AI开通VIP'][index]
      this.yanmoUrl = [
        `${import.meta.env.VITE_APP_YANMO_AI_URL}?channel=djyanbao&djToken=${localStorage.getItem('token')}&returnUrl=${encodeURIComponent(window.location.href)}`,
        `${import.meta.env.VITE_APP_YANMO_AI_URL}/vip?channel=djyanbao&djToken=${localStorage.getItem('token')}`,
      ][index]
      this.firstConfirmBtnName = [`去激活`, `去开通`][index]
      this.tipShow = true
    },
    clickQuestion({ question, answer }) {
      this.updateChats({ content: question })
      const chat = {
        type: 'gpt',
        content: '搜索中',
        completeContent: answer,
        params: { content: question },
        loading: true,
        timer: undefined,
        pageNums: [],
      }
      this.updateChats(chat)
      setTimeout(() => {
        chat.content = answer
        chat.loading = false
        this.updateChats(chat, 'update')
      }, 500)
    },
    getChatRecords() {
      const map = JSON.parse(this.getStorage(`chat-pdf-records-${this.userInfo.id}`) || '{}')
      return map[this.reportId] || []
    },
    setChatRecords() {
      const map = JSON.parse(this.getStorage(`chat-pdf-records-${this.userInfo.id}`) || '{}')
      const list =
        _.cloneDeep(this.chats)
          ?.map((item) => {
            if (item.type === 'gpt') {
              item.loading = false
              item.timer = undefined
              item.content = item.completeContent || item.content
            }
            return item
          })
          ?.filter((item) => item.content && item.content !== '搜索中') || []
      map[this.reportId] = list
      this.setStorage(`chat-pdf-records-${this.userInfo.id}`, JSON.stringify(map))
    },
    getChatHistory() {
      const map = JSON.parse(this.getStorage(`chat-pdf-history-${this.userInfo.id}`) || '{}')
      return (map[this.reportId] || []).splice(0, this.maxHistoryNum)
    },
    setChatHistory(data) {
      const map = JSON.parse(this.getStorage(`chat-pdf-history-${this.userInfo.id}`) || '{}')
      map[this.reportId] = data || []
      this.setStorage(`chat-pdf-history-${this.userInfo.id}`, JSON.stringify(map))
    },
    async quiz() {
      if (!this.hasYanmoAccount) {
        this.openYanmoTip()
        return
      }
      const content = this.question.trim()
      if (!content) {
        this.$message({
          type: 'error',
          message: '请先输入提问内容',
        })
        return
      }
      // 与猜你想问题目一样，直接使用答案
      if (this.qasMap[content]) {
        this.clickQuestion(this.qasMap[content])
        this.question = ''
        return
      }
      this.updateChats({ content })
      this.useGpt({ content })
      this.question = ''
    },
    async useGpt({ content }) {
      this.show = true
      const chat = {
        type: 'gpt',
        content: '搜索中',
        completeContent: '', // content是定时器一点一点输出的，保存一下完整的答案用于缓存聊天记录
        params: { content },
        loading: true,
        timer: undefined,
        pageNums: [],
      }
      this.updateChats(chat)
      try {
        const history = this.getChatHistory()
        const res = await this.$postYm('/chatdoc/chat', {
          history,
          question: content,
          djReportId: this.reportId,
          fileId: this.fileId,
          // stream: true, // 以后可能会支持流式
        })
        if (res.data?.code === 0) {
          chat.content = res?.data?.data?.text || ''
          this.setChatHistory([[content, chat.content], ...history])
          const pageNums =
            res?.data?.data?.sourceDocuments
              ?.filter((item) => {
                const pageIndex = item?.metadata?.pageIndex
                if (this.maxPageNum) {
                  return pageIndex && pageIndex <= this.maxPageNum
                }
                return pageIndex
              })
              ?.map((item) => item?.metadata?.pageIndex)
              ?.sort() || []
          chat.pageNums = [...new Set(pageNums)]
        } else if (res.data?.code === 10002) {
          chat.content = '发生错误，请重试，本次回答不扣除额度'
          this.openYanmoTip(1)
        } else {
          chat.content = '发生错误，请重试，本次回答不扣除额度'
          this.$message({
            type: 'error',
            message: res.data?.message,
          })
        }
        chat.loading = false
        this.updateChats(chat, 'update')
      } catch (err) {
        if (err?.response?.data?.code == 10002) {
          this.openYanmoTip(1)
          return
        } else if (err?.response?.data?.code > 10000) {
          this.$message({
            type: 'error',
            message: err?.response?.data?.message,
          })
        }
        chat.loading = false
        chat.content = '发生错误，请重试，本次回答不扣除额度'
        this.updateChats(chat, 'update', false)
      }
    },
    confirm() {},
    go2Yanmo() {
      window.open(`${import.meta.env.VITE_APP_YANMO_AI_URL}/know?channel=djyanbao&djToken=${localStorage.getItem('token')}`, '_blank')
    },
  },
}
</script>

<style lang="scss" scoped>
.chat-pdf-container {
  box-sizing: border-box;
  position: relative;
  width: 100%;
  height: 100%;
  background: #fff;
  border-radius: 8px;
  margin: auto;
  overflow: hidden;

  .fw600 {
    font-weight: 600;
  }

  .c-blue {
    color: #327bfa;
  }

  img {
    display: block;
    -webkit-user-drag: none;
  }

  .pointer {
    cursor: pointer;
  }

  .loading {
    margin-left: 1px;

    .dot1,
    .dot2,
    .dot3 {
      width: 3px;
      height: 3px;
      border-radius: 50%;
      margin: 0 3px 5px;
      background: #394551;
      opacity: 0.8;
    }

    .dot1 {
      animation: jump 1.4s -0.28s linear infinite;
    }

    .dot2 {
      animation: jump 1.4s -0.14s linear infinite;
    }

    .dot3 {
      animation: jump 1.4s linear infinite;
    }

    @keyframes jump {
      0%,
      80%,
      100% {
        transform: scale(0.68);
      }

      40% {
        transform: scale(1.2);
      }
    }
  }

  .scrollbar {
    box-sizing: border-box;
    position: relative;
    height: calc(100% - 68px - 56px);
    width: 100%;

    ::v-deep .el-scrollbar__wrap {
      height: calc(100% + 15px);
    }
  }

  .header {
    padding: 0 20px;
    height: 56px;
    border-bottom: 1px solid #eef2f6;
    .close {
      cursor: pointer;
      font-size: 20px;
    }

    & > img {
      width: 229px;
      height: 24px;
      cursor: pointer;
    }

    & > span {
      font-size: 12px;
      color: #327bfa;
    }
  }

  .title {
    padding: 20px 20px 16px;
    font-size: 14px;
    font-weight: 600;
    color: #202a33;
  }

  .report-detail {
    background: #f5f7f9;
    border-radius: 4px;
    margin: 0 auto;
    width: calc(100% - 40px);
    padding: 16px;

    .summary {
      &__title {
        font-size: 14px;
        font-weight: 600;
        color: #202a33;
      }

      &__content {
        font-size: 12px;
        color: #707d8a;
        margin-top: 12px;
        line-height: 20px;

        span {
          display: block;
          margin-bottom: 4px;
        }
      }
    }

    .chapter {
      margin-top: 16px;

      &__title {
        font-size: 14px;
        font-weight: 600;
        color: #202a33;
      }

      &__arrow-icon {
        font-size: 12px;
        color: #327bfa;
      }

      &__item-container {
        transition: height 0.1s;
        overflow: hidden;

        &--hide {
          height: 0 !important;
        }
      }

      &__item {
        margin-top: 12px;

        & > div {
          margin-bottom: 8px;
          font-size: 12px;
          color: #394551;
        }

        &__icon {
          width: 12px;
          height: 12px;
          margin-right: 4px;
        }

        &__introduce {
          position: relative;
          color: #707d8a;
          padding-left: 18px;

          &::after {
            content: '';
            position: absolute;
            top: calc(50% - 2.5px);
            left: 2px;
            width: 5px;
            height: 5px;
            background: #707d8a;
            border-radius: 50%;
          }
        }
      }
    }

    .tips {
      width: 100%;
      font-size: 12px;
      font-weight: 600;
      color: #327bfa;
      line-height: 36px;
      background: #eaf1fe;
      border-radius: 4px;
      border: 1px solid rgba(0, 0, 0, 0.1);
      text-align: center;
      margin-top: 16px;
    }
  }

  .question {
    padding: 20px;
    font-size: 14px;
    font-weight: 600;

    &__title {
      color: #202a33;
    }

    &__item {
      color: #327bfa;
      margin-top: 16px;
    }
  }

  .footer {
    width: 100%;
    height: 68px;
    background: #eaf1fe;
    padding: 0 20px;

    &__input {
      width: 350px;
      height: 40px;
      background: #ffffff;
      border-radius: 4px;
      font-size: 14px;
      color: #0b1013;
    }

    &__btn {
      cursor: pointer;
      width: 76px;
      line-height: 40px;
      background: #327bfa;
      border-radius: 20px;
      font-size: 14px;
      color: #ffffff;
      text-align: center;
    }
  }

  .btn {
    cursor: pointer;
    padding: 0 8px;
    background: rgba(255, 255, 255, 0);
    border-radius: 14px;
    border: 1px solid #1890ff;
    text-align: center;
    margin-left: 16px;
    margin-bottom: 19px;
  }

  .chat {
    position: relative;
    box-sizing: border-box;
    padding: 24px 20px;
    font-size: 14px;
    color: #394551;

    .ml12 {
      margin-left: 12px;
    }

    .right {
      text-align: right;
    }

    &__stop {
      cursor: pointer;
      width: 88px;
      background: #fff1f0;
      border-radius: 11px;
      font-size: 12px;
      color: #ff6262;
      line-height: 24px;
      margin: 0 auto;

      &-container {
        padding-bottom: 20px;
      }

      img {
        width: 14px;
        height: 14px;
        margin-right: 6px;
      }
    }

    &__title {
      margin-bottom: 10px;
      font-size: 12px;
      font-weight: 400;
      color: #707d8a;
      word-break: break-all;

      & > i {
        font-size: 14px;
        color: #327bfa;
        margin: 1px 6px 0 0;
      }
    }

    &__item {
      &__bg {
        background: #f5f7f9;
        border-radius: 4px;
        margin-bottom: 20px;
      }

      &__info {
        padding: 10px 14px;
        justify-content: flex-start;
        font-size: 12px;
        color: #0b1013;
        line-height: 20px;
      }

      &__img {
        width: 24px;
        height: 24px;
        border-radius: 6px;
      }

      &__content {
        word-break: break-all;
        box-sizing: border-box;
        max-width: 300px;
        margin: 2px 0 0 14px;
      }

      &__reference {
        padding: 10px 14px;
        font-size: 12px;
        color: #0b1013;
        border-top: 1px solid rgba(0, 0, 0, 0.08);

        .page {
          cursor: pointer;
          color: #327bfa;
        }
      }

      &--user {
        flex-direction: row-reverse;

        .chat__item__bg {
          background: #1890ff;
        }

        .chat__item__info {
          flex-direction: row-reverse;
        }

        .chat__item__content {
          margin: 2px 14px 0 0;
          color: #fff;
        }
      }
    }
  }
}
</style>
